--[[

    Filename:	auxtmr.lua

    Programmer:	aesiot

    Remark:

--]]

assert( "function" ~= type( on_timer ), "auxtmr initialize failed" )

local auxlib = require( "auxlib" )

local auxtmr = { _VERSION = "1.0" }

local Registry = { IdentMin = 0x10000, IdentMax = 0x80000, IdentCur = 0x10000 }

on_timer = function( ident )

	local value = Registry[ ident ]

	if value then

		if 0 == value[ 2 ] then

			Registry[ ident ] = nil
		end

		value[ 1 ]( ident )
	end
end

--------------------------------------------------------------------------------

function auxtmr.create( ident, value, interval, proc )

	if 'nil' == type( ident ) then

		local r, e = false, 'unknown error'

		local loop_end = Registry.IdentCur

		while not r do

			ident = Registry.IdentCur

			r, e = timer_create( ident, value, interval or 0 )

			Registry.IdentCur = ident + 1

			if Registry.IdentCur > Registry.IdentMax then

				Registry.IdentCur = Registry.IdentMin
			end

			auxlib.assert( r or Registry.IdentCur ~= loop_end, e )
		end
	else

		auxlib.assert( timer_create( ident, value, interval or 0 ) )
	end

	if proc then

		Registry[ ident ] = { proc, interval or 0 }
	end

	return ident
end

--------------------------------------------------------------------------------

function auxtmr.remove( ident )

	auxlib.assert( timer_remove( ident ) )

	Registry[ ident ] = nil
end

--------------------------------------------------------------------------------

function auxtmr.remove_and_activate( ident )

	auxlib.assert( timer_remove( ident ) )

	on_timer( ident )

	Registry[ ident ] = nil
end

--------------------------------------------------------------------------------

function auxtmr.create_with_date( tm, fn, id )

	auxlib.argcheck( auxlib.is_integer( tm ) or auxlib.is_table( tm ) , 1, "(table or integer expected)" )

	auxlib.argcheck( auxlib.is_integer( id ) or auxlib.is_nil( id ), 3, "(integer expected)" )

	if "table" == type( tm ) then

		tm = os.time( tm )
	end

	return auxtmr.create( id, 0, 300, function( ident )

		if os.time() == tm then

			auxtmr.remove( ident )

			auxlib.pcall( fn )
		end
	end )
end

--------------------------------------------------------------------------------

function auxtmr.create_with_week( wd, hour, min, sec, fn, id )

	auxlib.argcheck( auxlib.is_integer( wd   ), 1, "(integer expected)" )
	auxlib.argcheck( auxlib.is_integer( hour ), 2, "(integer expected)" )
	auxlib.argcheck( auxlib.is_integer( min  ), 3, "(integer expected)" )
	auxlib.argcheck( auxlib.is_integer( sec  ), 4, "(integer expected)" )

	auxlib.argcheck( auxlib.is_integer( id ) or auxlib.is_nil( id ), 6, "(integer expected)" )

	local tick = 0

	return auxtmr.create( id, 0, 300, function( ident )

		local r = os.date( "*t" )

		local m = ( 1 << ( r.wday - 1 ) )

		if m == ( wd & m ) and hour == r.hour and min == r.min and sec == r.sec then

			local now = os.get_tick_count()

			if now - tick > 1000 then

				tick = now

				auxlib.pcall( fn, ident )
			end
		end
	end )
end

--------------------------------------------------------------------------------

function auxtmr.create_timeout_container( interval, defvalue, timeout_handler )

	auxlib.argcheck( auxlib.is_integer( interval ), 1, "(integer expected)" )
	auxlib.argcheck( auxlib.is_integer( defvalue ), 2, "(integer expected)" )

	auxlib.argcheck( auxlib.is_function( timeout_handler ) or auxlib.is_none( timeout_handler ), 3, "(function expected)" )

	local registry = {}
	local opmethod = {}

	auxtmr.create( nil, 0, interval, function( ident )

		if nil == registry then

			auxtmr.remove( ident )
		else

			local rmlst = {}

			local now = os.get_tick_count()

			for k, v in pairs( registry ) do

				if now >= v.Tick then

					rmlst[ k ] = v.Data
				end
			end

			for k, v in pairs( rmlst ) do

				registry[ k ] = nil

				auxlib.pcall( timeout_handler, v )
			end
		end
	end )

	function opmethod.clear( notify )

		if notify then

			for k, v in pairs( registry ) do

				auxlib.pcall( timeout_handler, v.Data )
			end
		end

		registry = {}
	end

	function opmethod.close()

		registry = nil
	end

	function opmethod.for_each( handler )

		for k, v in pairs( registry ) do

			if handler( k, v.Data ) then

				return k, v.Data
			end
		end
	end

	function opmethod.insert( index, value, timeout, overwrite )

		if nil == index or nil == value then

			return false
		end

		if not overwrite and registry[ index ] ~= nil then

			return false
		end

		if not auxlib.is_integer( timeout ) then

			timeout = defvalue
		end

		registry[ index ] = { Data = value, Tick = os.get_tick_count() + timeout }

		return true
	end

	function opmethod.insert_raw( index, value, clock, overwrite )

		if nil == index or nil == value or ( not auxlib.is_integer( clock ) ) then

			return false
		end

		if not overwrite and registry[ index ] ~= nil then

			return false
		end

		registry[ index ] = { Data = value, Tick = clock }

		return true
	end

	function opmethod.remove( index )

		if nil == index then

			return nil
		end

		local value = registry[ index ]

		registry[ index ] = nil

		return value and value.Data
	end

	function opmethod.search( index )

		local value = registry[ index ]

		return value and value.Data
	end

	return opmethod
end

--------------------------------------------------------------------------------

function auxtmr.create_timeout_queue( interval, defvalue, nextproc, timedout )

	auxlib.argcheck( auxlib.is_integer( interval ), 1, "(integer expected)" )
	auxlib.argcheck( auxlib.is_integer( defvalue ), 2, "(integer expected)" )

	local thentick = nil
	local registry = {}
	local opmethod = {}

	local function timer_routine( ident )

		if nil == registry then

			auxtmr.remove( ident )
		else

			local last = #registry

			local item = registry[ last ]

			if item and thentick then

				if os.get_tick_count() - thentick < item.Time then

					return
				end

				thentick = nil

				table.remove( registry, last )

				auxlib.pcall( timedout, item.Data )

				item = registry[ last - 1 ]
			end

			if item then

				auxlib.pcall( nextproc, item.Data )

				thentick = os.get_tick_count()
			end
		end
	end

	auxtmr.create( nil, 0, interval, timer_routine )

	function opmethod.clear( notify )

		if notify then

			for k, v in pairs( registry ) do

				auxlib.pcall( timedout, v.Data )
			end
		end

		registry = {}
	end

	function opmethod.close()

		registry = nil
	end

	function opmethod.front()

		local item = registry[ #registry ]

		return ( item and item.Data ) or nil
	end

	function opmethod.pop()

		thentick = nil

		local item = table.remove( registry, #registry )

		timer_routine()

		return ( item and item.Data ) or nil
	end

	function opmethod.push( value, timeout )

		if nil == value then

			return false
		end

		if not auxlib.is_integer( timeout ) then

			timeout = defvalue
		end

		table.insert( registry, 1, { Data = value, Time = timeout } )

		timer_routine()
	end

	function opmethod.replace( value, timeout, update )

		registry[ #registry ] = { Data = value, Time = timeout }

		if update then

			thentick = os.get_tick_count()
		end
	end

	return opmethod
end

--------------------------------------------------------------------------------

return auxtmr
